package cz.cuni.mff.d3s.arcas.m2m

import cz.cuni.mff.d3s.arcas.drdl.*
import cz.cuni.mff.d3s.arcas.intermediate.*
import java.util.*
import com.google.inject.Inject

class DRDLToIntermediate {

	def create target : IntermediateFactory::eINSTANCE.createIntermediateDocument transform(Iterable<AbstractElement> elements) {
		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.Connector))) {
			target.connectors += transformConnector(element, elements)
		}
		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ConnectorArchitecture))) {
			target.distributionArchitectures += transformConnectorArchitecture(element, elements)
		}
		for (element : elements.filter(typeof(Dock))) {
			target.docks += transformDock(element)
		}
		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ElementArchitecture))) {
			target.elementArchitectures += transformElementArchitecture(element, elements)
		}
		for (element : elements.filter(typeof(ElementType))) {
			target.ports += element.providedPorts.map(x|x.transformPort("LocalProvidedPort"))
			target.ports += element.requiredPorts.map(x|x.transformPort("LocalRequiredPort"))
			target.ports += element.remotePorts.map(x|x.transformPort("RemotePort"))
		}
		for (element : elements.filter(typeof(CommunicationStyle))) {
			target.roles += element.roles.map(x|x.transformRole)
		}
		for (element : elements.filter(typeof(Component))) {
			target.signatures += getSignaturesFromComponent(element)
		}
		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ElementArchitecture))) {
			target.signatures += getSignaturesFromSignaturePropagations(element.signaturePropagations)
		}
		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ElementArchitecture))) {
			target.subelements += element.subElements.map(x|x.transformSubElement)
		}
		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ConnectorArchitecture))) {
			target.units += element.units.map(x|x.transformUnit)
		}
	}

	def transformPort(cz.cuni.mff.d3s.arcas.drdl.Port source, String type) {
		var port = createPortFromName(source.name)
		port.type = type
		return port
	}

	// TODO: Pridat kontrolo do DRDL, ze nesmu byt rovnako pomenovane provided a required porty.
	def create target : IntermediateFactory::eINSTANCE.createPort createPortFromName(String name) {
		target.name = name
	}

	def create target : IntermediateFactory::eINSTANCE.createDeploymentDock transformDock(Dock source) {
		target.name = source.name
	}

	def create target : IntermediateFactory::eINSTANCE.createRole transformRole(cz.cuni.mff.d3s.arcas.drdl.Role source) {
		target.name = source.name
	}

	def create target : IntermediateFactory::eINSTANCE.createElementArchitecture transformElementArchitecture(cz.cuni.mff.d3s.arcas.drdl.ElementArchitecture source, Iterable<AbstractElement> elements) {
		// Find ElementType corresponding to ElementArchitecture source.
		var elementType = elements.filter(typeof(ElementType)).filter(x|x.name == source.elementType.name).head

		target.name = source.name
		target.availablePorts += getAvailablePorts(elementType)
		target.subElements += source.subElements.map(x|x.transformSubElement(elements))
		target.portBindings += source.bindings.map(x|x.transformBinding)
		target.allowedDeployments += getAllowedDocks(source.capabilities, elements.filter(typeof(Dock)))
		target.signaturePropagations += transformSignaturePropagations(source.signaturePropagations)
		target.features += source.features.filter(typeof(ElementFeature)).map(x|x.transformExplicitFeature)
		target.features += source.features.filter(typeof(SubelementFeature)).map(x|x.transformDelegatedFeature)
	}

	def getAvailablePorts(ElementType type) {
		var ret = new ArrayList<String>()
		ret += type.providedPorts.map(x|x.name)
		ret += type.requiredPorts.map(x|x.name)
		ret += type.remotePorts.map(x|x.name)
		return ret
	}

	def create target : IntermediateFactory::eINSTANCE.createElementArchitectureSubElement transformSubElement(SubElement source, Iterable<AbstractElement> elements) {
		target.name = source.name

		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ElementArchitecture)).filter(x|x.elementType.name == source.type.name)) {
			target.allowedEAs += element.name;
		}
	}

	def create target : IntermediateFactory::eINSTANCE.createElementArchitecturePortBinding transformBinding(PortBinding source) {
		target.ffea1 = if (source.element1 == null) "this" else source.element1.name
		target.ffea2 = if (source.element2 == null) "this" else source.element2.name
		target.eap1 = source.port1
		target.eap2 = source.port2
	}

	def getAllowedDocks(Iterable<DockCapability> requirements, Iterable<Dock> docks) {
		var ret = new ArrayList<String>()
		if (!requirements.empty) {
			for (dock : docks) {
				if (isOMGDCCompatible(dock.capabilities, requirements))
					ret += dock.name;
			}
		} else {
			for (dock : docks) {
				ret += dock.name;
			}
		}
		return ret
	}

	def isOMGDCCompatible(Iterable<DockCapability> capabilities, Iterable<DockCapability> requirements) {
		for (requirement : requirements) {
			for (capability : capabilities) {
				if (requirement.name == capability.name) {
					if (requirement.value != capability.value)
						return false
				}
			}
		}

		return true
	}

	def transformSignaturePropagations(Iterable<SignaturePropagation> signaturePropagations) {
		var ret = new ArrayList<ElementArchitectureSignaturePropagation>()
		var toProcess = new ArrayList<SignaturePropagation>()
		var placeholderPorts = new HashMap<String,String>()

		// Store port name for each first use of placeholder in form "port: $placeholder".
		// Store all other signature propagations to toProcess queue.
		for (sp : signaturePropagations) {
			if (sp.value.placeholder != null &&
				!placeholderPorts.containsKey(sp.value.placeholder)
			) {
				placeholderPorts.put(sp.value.placeholder, sp.port)
			} else {
				toProcess += sp
			}
		}

		for (sp : toProcess) {
			ret += transformSignaturePropagation(sp, placeholderPorts)
		}

		return ret;
	}

	def create target : IntermediateFactory::eINSTANCE.createElementArchitectureSignaturePropagation transformSignaturePropagation(SignaturePropagation source, Map<String,String> placeholderPorts) {
		target.portName = source.port
		if (source.value.placeholder != null) {
			target.destPortName = placeholderPorts.get(source.value.placeholder)
		} else {
			target.destSignature = source.value.name
			target.destSignatureAttributes += source.value.parameters.map(x|x.transformSignatureParameter2NameValuesPair(placeholderPorts))
		}
	}

	def create target : IntermediateFactory::eINSTANCE.createNameValuesPair transformSignatureParameter2NameValuesPair(SignatureParameter source, Map<String,String> placeholderPorts) {
		target.name = source.name
		if (source.value.startsWith("$")) {
			target.ports += placeholderPorts.get(source.value)
		} else {
			target.values += source.value
		}
	}

	def create target : IntermediateFactory::eINSTANCE.createElementArchitectureFeature transformExplicitFeature(ElementFeature source) {
		target.name = source.name
		target.value = source.term.name
		if (source.term.variable != null)
			target.attributes += transformFeatureTerm2FeatureAttribute(source.term)
	}

	def create target : IntermediateFactory::eINSTANCE.createElementArchitectureFeatureAttribute transformFeatureTerm2FeatureAttribute(FeatureTermWithPlaceholders source) {
		target.name = source.variable
		target.value = source.value
	}

	def create target : IntermediateFactory::eINSTANCE.createElementArchitectureFeature transformDelegatedFeature(SubelementFeature source) {
		target.name = source.featureName
		target.delegatedToSubElement = source.element.name
		target.delegatedToFeatureName = source.subElementFeature
	}

	def getSignaturesFromComponent(Component component) {
		var signatures = new ArrayList<cz.cuni.mff.d3s.arcas.intermediate.Signature>()

		for (ifc : component.providedInterfaces)
			signatures += createSignatureFromName(ifc.signature)
		for (ifc : component.requiredInterfaces)
			signatures += createSignatureFromName(ifc.signature)

		return signatures
	}

	def getSignaturesFromSignaturePropagations(Iterable<SignaturePropagation> signaturePropagations) {
		var signatures = new ArrayList<cz.cuni.mff.d3s.arcas.intermediate.Signature>()

		for (sp : signaturePropagations) {
			if (sp.value.placeholder == null) {
				// Add signature with optional parameters.
				var signature = createSignatureFromName(sp.value.name)
				signature.parameters += sp.value.parameters.map(x|x.name)
				signatures += signature

				// Add sub-signatures.
				// TODO: Na tomto mieste by sa mali rekurzivne pridat podsignatury, avsak definovane to tak nie je.
				signatures += sp.value.parameters.filter(x|!x.value.startsWith("$")).map(x|x.value.createSignatureFromName)
			}
		}

		return signatures
	}

	def create target : IntermediateFactory::eINSTANCE.createSignature createSignatureFromName(String name) {
		target.name = name
	}

	def create target : IntermediateFactory::eINSTANCE.createDistributionArchitecture transformConnectorArchitecture(cz.cuni.mff.d3s.arcas.drdl.ConnectorArchitecture source, Iterable<AbstractElement> elements) {
		target.name = source.name
		target.roles += source.communicationStyle.roles.map(x|x.transformCARole)
		target.units += source.units.map(x|x.transformUnit(elements, source))
		target.remoteBindings += source.unitBindings.map(x|x.transformBinding)
		target.features += source.features.map(x|x.transformUnitFeature)
	}

	def create target : IntermediateFactory::eINSTANCE.createDistributionArchitectureRole transformCARole(cz.cuni.mff.d3s.arcas.drdl.Role source) {
		target.name = source.name
		target.multipleCardinality = source.upperBound != "1"
	}

	def create target : IntermediateFactory::eINSTANCE.createDistributionArchitectureUnit transformUnit(cz.cuni.mff.d3s.arcas.drdl.Unit source, Iterable<AbstractElement> elements, cz.cuni.mff.d3s.arcas.drdl.ConnectorArchitecture ca) {
		target.name = source.name
		target.multipleCardinality = source.upperBound != "1"
		target.associatedWithPort = ca.portRoles.filter(x|x.unit == source.name).head.port
		target.associatedWithRole = ca.portRoles.filter(x|x.unit == source.name).head.asWhat

		for (element : elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ElementArchitecture)).filter(x|x.elementType.name == source.type.name)) {
			target.allowedEAs += element.name;
		}
	}

	def create target : IntermediateFactory::eINSTANCE.createDistributionArchitectureRemoteBinding transformBinding(UnitBinding source) {
		target.cau1 = source.unit1
		target.cau2 = source.unit2
		target.eapr1 = source.port1
		target.eapr2 = source.port2
	}

	def create target : IntermediateFactory::eINSTANCE.createDistributionArchitectureFeatureDelegation transformUnitFeature(UnitFeature source) {
		target.featureName = source.featureName
		target.unit = source.unit.name
		target.unitFeatureName = source.elementTypeFeature
	}

	def create target : IntermediateFactory::eINSTANCE.createUnit transformUnit(cz.cuni.mff.d3s.arcas.drdl.Unit source) {
		target.name = source.name
	}

	def transformSubElement(SubElement source) {
		createSubelementFromName(source.name)
	}

	def create target : IntermediateFactory::eINSTANCE.createSubelement createSubelementFromName(String name) {
		target.name = name
	}

	def create target : IntermediateFactory::eINSTANCE.createConnector transformConnector(cz.cuni.mff.d3s.arcas.drdl.Connector source, Iterable<AbstractElement> elements) {
		target.name = source.name
		target.endpoints += source.endpoints.map(x|x.transformEndpoint(elements))
		target.features += source.features.map(x|x.transformFeature)
		target.allowedDAs += elements.filter(typeof(cz.cuni.mff.d3s.arcas.drdl.ConnectorArchitecture)).filter(x|x.communicationStyle.name == source.communicationStyle.name).map(x|x.name)
	}

	def create target : IntermediateFactory::eINSTANCE.createConnectorEndpoint transformEndpoint(cz.cuni.mff.d3s.arcas.drdl.ConnectorEndpoint source, Iterable<AbstractElement> elements) {
		target.name = source.interface.component + "." + source.interface.interface
		target.role = source.role

		for (component : elements.filter(typeof(Component)).filter(x|x.name == source.interface.component)) {
			// TODO: Nikde nevyuzivame fakt, ze komponenta specifikuje "requires" alebo "provides".
			for (ifc : component.providedInterfaces.filter(x|x.name == source.interface.interface))
				target.signature = ifc.signature;
			for (ifc : component.requiredInterfaces.filter(x|x.name == source.interface.interface))
				target.signature = ifc.signature;
		}

		for (allocation : elements.filter(typeof(Allocation))) {
			for (componentAllocation : allocation.allocations.filter(x|x.component.name == source.interface.component)) {
				target.dock = componentAllocation.dock.name
			}
		}
	}

	def create target : IntermediateFactory::eINSTANCE.createConnectorFeature transformFeature(Feature source) {
		target.name = source.name.name
		if (source.name.qualifiedName != null) {
			target.component = source.name.qualifiedName.component
			target.interface = source.name.qualifiedName.interface
		}
		target.values += source.terms.map(x|x.transformFeatureTerm)
	}

	def create target : IntermediateFactory::eINSTANCE.createConnectorFeatureValue transformFeatureTerm(FeatureTerm source) {
		target.name = source.name

		if (source.variable != null)
			target.attributes += transformFeatureTerm2FeatureAttribute(source)
	}

	def create target : IntermediateFactory::eINSTANCE.createConnectorFeatureValueAttribute transformFeatureTerm2FeatureAttribute(FeatureTerm source) {
		target.name = source.variable
		target.value = source.value
	}

}
